﻿using System;
using System.Text;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.Iterations;

namespace SoftwareConsulting.BI11.NumericalMethods.Optimization
{
    /// <summary>
    /// Класс оптимизатора однопеременных функций
    /// </summary>
    /// <remarks>Используется метод золотого сечения</remarks>
    public class OneVariableOptimizer : FunctionalIterator
    {
        private readonly static double _goldenSection = (3 - Math.Sqrt(5)) / 2;	    
        private SuspiciousOptimalPoint[] _bestPoints = null;//наиболее подходящие точки	    
        private SuspiciousOptimalPointAndVectorFactory _pointsFactory;//фабрика точек (для разграничения цели поиска: минимум или максимум)

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="function">Функция, требующая решения</param>
        /// <param name="pointsCreator">Фабрика точек</param>
        public OneVariableOptimizer(IOneVariableFunction function, SuspiciousOptimalPointAndVectorFactory pointsCreator)
                    : base(function)
        {
            _pointsFactory = pointsCreator;
        }

        /// <summary>
        /// Возвращает относительную погрешность
        /// </summary>
        /// <returns></returns>
        private double ComputeRelativeError()
        {
            return GetRelativeError(Math.Abs(_bestPoints[2].Argument
                                                - _bestPoints[1].Argument),
                                      Math.Abs(_bestPoints[0].Argument));
        }

        /// <summary>
        /// Выполняет итерацию
        /// </summary>
        /// <returns>Текущая погрешность результата</returns>
        public override double DoIteration()
        {
            if (_bestPoints[2].Argument - _bestPoints[1].Argument > _bestPoints[1].Argument - _bestPoints[0].Argument)
                DoBisection(2);
            else
                DoBisection(0);
            SetBestZeroApproximation(_bestPoints[1].Argument);
            return ComputeRelativeError();
        }

        /// <summary>
        /// Создает случайную популяцию
        /// </summary>
        public override void CreateRandomPopulation()
        {
            OptimalSegmentFinder segmentFinder = new OptimalSegmentFinder(Function, _pointsFactory);
            segmentFinder.InitialBestZeroApproximation = BestZeroApproximation;
            segmentFinder.Evaluate();
            _bestPoints = segmentFinder.BestPoints;
        }

        /// <summary>
        /// Выполняет половинное деление между 1й и Nй точками
        /// </summary>
        /// <param name="worstPointIndex">Индекс худшей точки поделенного интервала</param>
        private void DoBisection(int worstPointIndex)
        {
            double x = _bestPoints[1].Argument;
            x += _goldenSection * (_bestPoints[worstPointIndex].Argument - x);
            SuspiciousOptimalPoint newPoint = _pointsFactory.CreatePoint(x, Function);
            if (newPoint.BetterThan(_bestPoints[1]))
            {
                _bestPoints[2 - worstPointIndex] = _bestPoints[1];
                _bestPoints[1] = newPoint;
            }
            else
                _bestPoints[worstPointIndex] = newPoint;
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(
                string.Format("{0} iterations, precision = {1}", this.IterationsCount, this.RealPrecision ) );
            for (int i = 0; i < _bestPoints.Length; i++)
            {
                sb.Append('\n');
                sb.Append(_bestPoints[i]);
            }
            return sb.ToString();
        }
    }
}
